#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <cstdlib>//atoi
#include <unordered_map>
#include <algorithm>//transform
#include <sys/sendfile.h> //sendfile
#include <sys/stat.h>//stat
#include <sys/wait.h>//waitpid
#include <fcntl.h> //open
#include <sstream>//stringstream
#include "Util.hpp"
#include"Log.hpp"

#define SEP ": "//分隔符
#define WEB_ROOT "./wwwroot"
#define HOME_PAGE "index.html"
#define HTTP_VERSION "HTTP/1.0"
#define LINE_END "\r\n"

#define PAGE_404 "404.html"

#define OK 200 //状态码
#define BAD_REQUEST 400
#define NOT_FOUND 404
#define SERVER_ERROR 500

static std::string Code2Desc(int code)
{
    std::string Desc;
    switch(code)
    {
        case 200:
            Desc = "OK";
            break;
        case 404:
            Desc = "NOT_FOUND";
            break;
        default:
            break;
    }
    return Desc;
}
static std::string Suffix2Desc(const std::string &suffix)
{
    static std::unordered_map<std::string, std::string> suffix2desc = {
        {".html", "text/html"},
        {".css", "text/css"},
        {".js", "application/x-javascript"},
        {".jpg", "application/x-jpg"},
        {".xml", "application/xml"}
    };

    auto iter = suffix2desc.find(suffix);
    if(iter != suffix2desc.end())
    {
        return iter->second;
    }

    return "text/html";//默认返回
}

struct HttpRequest//请求协议
{
    std::string request_line;//请求行
    std::vector<std::string> request_header;//报头
    std::string blank;//空行
    std::string request_body; //报文

    //解析完毕之后的结果
    std::string method;//请求方法
    std::string uri; //path?args
    std::string version;//版本

    std::unordered_map<std::string, std::string> header_kv;//报头属性和对应的“值”
    int content_length;

    std::string path;//请求资源
    std::string suffix;//请求文件的后缀
    std::string query_string;//状态行参数
    bool cgi;
public:
    HttpRequest():content_length(0), cgi(false) {}
    ~HttpRequest(){}
};


struct HttpResponse
{
    std::string status_line;//状态行
    std::vector<std::string> response_header;//报头
    std::string blank;//空行
    std::string response_body;//报文

    int status_code;//状态码
    int fd;
    int file_size;
public:
    HttpResponse():blank(LINE_END), status_code(OK), fd(-1), file_size(0){}
    ~HttpResponse(){}
};
//读取请求，分析请求，构建响应
//IO通信
class EndPoint
{
private:
    int sock;
    HttpRequest http_request;
    HttpResponse http_response;
    bool stop;//是否退出
private:
    bool RecvHttpRequestLine()//读取请求行
    {
        auto &line = http_request.request_line;
        if(Util::ReadLine(sock, line) > 0)
        {
            line.resize(line.size()-1);//去掉\n
            LOG(INFO, http_request.request_line);
        }
        else
        {//读取出错
            stop = true;
        }
        return stop;
    }
    bool RecvHttpRequestHeader()//读取请求报头
    {
        std::string line;
        while(true)
        {
            line.clear();
            if(Util::ReadLine(sock, line) <= 0)//读取错误
            {
                stop = true;//应该停止
                break;
            }
            if(line == "\n")//读到了空行
            {
                http_request.blank = line; //空行   
                break;
            }
            line.resize(line.size()-1);//去除末尾的\n
            http_request.request_header.push_back(line);
        }
        return stop;
    }
    void ParseHttpRequestLine()//解析请求行
    {
        auto& line = http_request.request_line;
        std::stringstream ss(line);
        ss >> http_request.method >> http_request.uri >> http_request.version;
        auto& method = http_request.method;
        transform(method.begin()/*转化起始*/, method.end()/*转化结尾*/, method.begin()/*结果放在哪里*/, ::toupper/*转大写*/);  

        LOG(INFO, http_request.method);
        LOG(INFO, http_request.uri);
        LOG(INFO, http_request.version);
    }
    void ParseHttpRequestHeader()//解析请求报头
    {
        std::string key;
        std::string value;
        for(auto& iter : http_request.request_header)
        {
            if(Util::CutString(iter, key, value, SEP))
            {
                http_request.header_kv.insert({key, value});//std::make_pair(key, value)
            }
            // std::cout<< "debug " << key << std::endl;
            // std::cout<< "debug " << value << std::endl;
        }
    }
    bool IsNeedRecvHttpRequestBody()//判断是否需要读取正文
    {
        auto method = http_request.method;
        if(method == "POST")
        {
            const auto &header_kv = http_request.header_kv;
            auto iter = header_kv.find("Content-Length");//正文大小
            if(iter != header_kv.end())
            {
                http_request.content_length = atoi(iter->second.c_str());
                return true;
            }
        }
        return false;
    }
    bool RecvHttpRequestBody()//读取正文
    {
        if(IsNeedRecvHttpRequestBody())
        {
            int content_lenght = http_request.content_length;
            auto &body = http_request.request_body;

            char ch = 0;
            while(content_lenght)
            {
                //前面已经调用过读取，已经把请求行和报头、空行读走了
                //现在剩下报文没有读取
                ssize_t s = recv(sock, &ch, 1, 0);//逐个字符读取
                if(s>0){
                    body.push_back(ch);
                    content_lenght--;
                }
                else{
                    stop = true;
                    break;
                }
            }
        }
        return stop;
    }
    int ProcessCgi()
    {
        int code = OK;
        //父进程数据
        auto &method = http_request.method;
        auto &query_string = http_request.query_string; //GET方法
        auto &body_text = http_request.request_body; //POST方法
        int content_length = http_request.content_length;//保存正文长度
        auto &response_body = http_response.response_body;

        std::string query_string_env;//GET参数环境变量
        std::string method_env;//方法环境变量
        std::string content_length_env;//正文长度环境变量
        
        auto &bin = http_request.path; //要让子进程执行的目标可执行程序，一定存在
        //站在父进程的角度命名，input用来读，output用来写
        //0是读，1是写
        int input[2];
        int output[2];
        if(pipe(input) < 0){
            LOG(ERROR, "pipe input error");
            code = SERVER_ERROR;
            return code;
        }
        if(pipe(output) < 0){
            LOG(ERROR, "pipe output error");
            code = SERVER_ERROR;
            return code;
        }
        
        //创建子进程进行进程替换
        pid_t pid = fork();
        if(pid == 0)//child
        {
            //子进程用 input[1] 写， 用output[0]读取
            close(input[0]);
            close(output[1]);

            method_env = "METHOD=";
            method_env += method;
            putenv((char*)method_env.c_str());//把用的方法放入环境变量里
            //便于后续替换的cgi程序能够知道请求方法

            if("GET" == method)//GET方法：把参数导入到环境变量里
            {
                query_string_env = "QUERY_STRING=";
                query_string_env += query_string;
                putenv((char*)query_string_env.c_str());
            }
            else if(method == "POST")//POST方法：把正文长度导入到环境变量里
            {
                content_length_env = "CONTENT_LENGTH=";
                content_length_env += std::to_string(content_length);
                putenv((char*)content_length_env.c_str());
                LOG(INFO, "POST Method, Add Content_Length Env");
            }
            else
            {
                //Do Nothing
            }

            //子进程角度
            //output[0] 读取 重定向 从fd==0读取
            //input[1] 写入 重定向 从fd==1写入
            dup2(output[0], 0);
            dup2(input[1], 1);
            //替换成功后，目标子进程从0读，从1写即可(重定向)
            execl(bin.c_str(), bin.c_str(), nullptr);
            exit(1);
        }
        else if(pid < 0){//error
            LOG(ERROR, "fork error!");
            code = 404;
            return code;
        }
        else//pid > 0
        {
            //parent
            close(input[1]);
            close(output[0]);

            if("POST" == method)
            {
                const char* start = body_text.c_str();
                int total = 0;
                int size = 0;
                while( total < content_length && (size = write(output[1], start+total, body_text.size()-total)) )
                {//保证能全部写入
                    total += size;
                }
            }
            char ch = 0;
            while(read(input[0], &ch, 1) > 0)
            {
                response_body.push_back(ch);
            }

            int status = 0;//进程退出码
            pid_t ret = waitpid(pid, &status, 0);//这是一个新线程，阻塞等待不影响主线程
            if(ret == pid)
            {
                if(WIFEXITED(status))//检测进程退出是正常的
                {
                    if(WEXITSTATUS(status) == 0) //退出码是0
                    {
                        code = OK;
                    }
                    else//退出结果不正确
                    {
                        code = BAD_REQUEST;//客户端传参不正确
                    }
                }
                else//异常退出
                {
                    code = SERVER_ERROR;
                }
            }

            close(input[0]);
            close(output[1]);
        }
        return code;
    }

    int ProcessNonCgi()//非Cgi处理构建响应
    {
        http_response.fd = open(http_request.path.c_str(), O_RDONLY);//打开文件，发送内容
        if(http_response.fd >= 0)
        {
            return OK;//文件打开成功
        }
        return NOT_FOUND;//文件打开失败
    }
    void BuildOkResponse()
    {
        std::string line = "Content-Type: ";
        line += Suffix2Desc(http_request.suffix);
        line += LINE_END;
        http_response.response_header.push_back(line);
        
        line = "Content-Length: ";
        if (http_request.cgi)
        {//POST:正文大小
            line += std::to_string(http_response.response_body.size());
        }
        else
        {//GET:文件大小
            line += std::to_string(http_response.file_size);
        }
        line += LINE_END;
        http_response.response_header.push_back(line);
    }
    void HandlerError(std::string page)
    {
        http_request.cgi = false;//cgi出错也要返回文件

        //给用户返回对应的404页面
        http_response.fd = open(page.c_str(), O_RDONLY);
        if(http_response.fd > 0)
        {
            struct stat st;
            stat(page.c_str(), &st);
            http_response.file_size = st.st_size;//更新错误文件的大小
            
            std::string line = "Content-Type: text/html";
            line += LINE_END;
            http_response.response_header.push_back(line);
            
            line = "Content-Length: ";
            line += std::to_string(st.st_size);
            line += LINE_END;
            http_response.response_header.push_back(line);
        }
    }
    void BuildHttpResponseHelper()
    {
        auto &code = http_response.status_code;

        //状态行构建
        auto &status_line = http_response.status_line;
        status_line += HTTP_VERSION;
        status_line += " ";
        status_line += std::to_string(code);
        status_line += " ";
        status_line += Code2Desc(code);
        status_line += LINE_END;

        //构建响应正文，可能包括构建响应报头
        std::string path = WEB_ROOT;
        path += "/";
        switch(code)
        {
            case OK:
                BuildOkResponse();
                break;
            case NOT_FOUND:
                path += PAGE_404;
                HandlerError(path); //返回错误网页
                break;
            case BAD_REQUEST:
                path += PAGE_404;
                HandlerError(path); //返回错误网页
                break;
            case SERVER_ERROR:
                path += PAGE_404;
                HandlerError(path); //返回错误网页
                break;
            default:
                break;
        }
    }
public:
    EndPoint(int _sock):sock(_sock), stop(false)
    {}
    ~EndPoint()
    {
        close(sock);
    }
    bool IsStop()
    {
        return stop;
    }
    void RecvHttpRequest()//读取请求并分析请求
    {
        //任意一个返回true都说明 stop为真，退出分析
        if(RecvHttpRequestLine() || RecvHttpRequestHeader())
        {//recv error
        }
        else
        {//分析请求
            ParseHttpRequestLine();
            ParseHttpRequestHeader();
            RecvHttpRequestBody();//读取请求正文
        }
    }
    //状态码决定报文的响应方式
    void BuildHttpResponse()//建立响应，状态行，响应报头，空行，响应正文
    {
        //请求已经全部读完，即可以直接构建响应了
        struct stat st;//用于获取文件属性

        std::string _path;
        std::size_t found=0;//文件后缀标记的下标
        auto &code = http_response.status_code;
        if(http_request.method != "GET" && http_request.method != "POST")
        {
            //非法请求
            LOG(WARNING, "method is not right");
            code = BAD_REQUEST;
            goto END;
        }
        //  /a/b/c?x=100&y=200
        //问号左侧是我们要访问的某种资源
        //问号右侧是我们上传的参数
        if(http_request.method == "GET")
        {
            ssize_t pos = http_request.uri.find('?');
            if(pos != std::string::npos)//带参
            {
                Util::CutString(http_request.uri, http_request.path, http_request.query_string, "?");
                http_request.cgi = true;
            }
            else//说明没有参数
                http_request.path = http_request.uri;
        }
        else if (http_request.method == "POST")
        {
            http_request.cgi = true;
            http_request.path = http_request.uri;
        }
        else{
        }
        // std::cout << "debug: uri: " << http_request.uri << std::endl;
        // std::cout << "debug: path: " << http_request.path << std::endl;
        // std::cout << "debug: query_string: " << http_request.query_string << std::endl;
        
        _path = http_request.path; //更新路径
        http_request.path = WEB_ROOT;
        http_request.path += _path;
        //判断path最后一位是不是'/'
        if(http_request.path[http_request.path.size()-1] == '/'){//web根目录
            http_request.path += HOME_PAGE;//默认访问首页
        }

        //获取文件属性
        if(stat(http_request.path.c_str(), &st) == 0)//资源存在
        {
            
            if(S_ISDIR(st.st_mode))
            {
                //说明请求的资源是一个目录，这是不被允许的，需要做一下相关处理
                //虽然是一个目录，但绝对不会以/结尾！上面有判断
                //所以这里要加上 / 
                http_request.path += "/";
                http_request.path += HOME_PAGE;

                stat(http_request.path.c_str(), &st);//更新文件
            }
            if( (st.st_mode&S_IXUSR) || (st.st_mode&S_IXGRP) || (st.st_mode&S_IXOTH) )//按标志位判断是否具有可执行权限
            {
                //特殊处理
                http_request.cgi = true;
            }
            http_response.file_size = st.st_size;//获得文件资源大小
        }
        else
        {
            //资源不存在
            LOG(WARNING, http_request.path + " Not Found");
            code = NOT_FOUND;
            goto END;
        }

        //访问的文件就在path
        //提取请求方请求的文件后缀
        found = http_request.path.rfind(".");
        if(found == std::string::npos)
        {
            http_request.suffix = ".html";
        }
        else
        {
            http_request.suffix = http_request.path.substr(found);
        }

        if(http_request.cgi)
        {
            code = ProcessCgi();//执行目标程序，拿到结果:http_response.response_body
        }
        else
        {
            //说明是GET方法，且不带参。
            //1. 目标文件资源存在
            //2. 返回并不是单单返回网页，而是要构建HTTP响应
            code = ProcessNonCgi();//简单的网页返回，返回静态网页,打开即可
        }
        
END:
        //END之上是 分析请求，完善响应的结构
        //下面是 统一构建响应
        BuildHttpResponseHelper();//填充 状态行、响应报头、空行、正文

    }
    void SendHttpResponse()//发送响应
    {
        send(sock, http_response.status_line.c_str(), http_response.status_line.size(), 0);
        for(auto& iter: http_response.response_header)
        {
            send(sock, iter.c_str(), iter.size(), 0);
        }
        send(sock, http_response.blank.c_str(), http_response.blank.size(), 0);
        
        //这里发送的有两种资源，1.response_body(cgi) 2.fd文件(非cgi)
        if(http_request.cgi)
        {//POST
            auto &response_body = http_response.response_body;
            ssize_t size = 0;//记录一次中发多少字节
            ssize_t total = 0;//记录总共发了多少字节
            const char* start = response_body.c_str();//调整起始发送位置
            while(total < response_body.size() && ((size = send(sock, start+total, response_body.size()-total, 0)) > 0) )
            {
                total += size;
            }
        }
        else
        {//GET
            sendfile(sock, http_response.fd, nullptr, http_response.file_size);
            close(http_response.fd);
        }
    }
};

class CallBack
{
public:
    CallBack(){}

    ~CallBack(){}
    void operator()(int sock)
    {
        HandlerRequest(sock);
    }
    void HandlerRequest(int sock)
    {
        LOG(INFO, "HandlerRequest ... begin");
#ifdef DEBUG
        //For Test
        char buffer[4096];
        recv(sock, buffer, sizeof(buffer), 0);
        std::cout<<"-----------------begin--------------------"<<std::endl;
        std::cout<< buffer<< std::endl;
        std::cout<<"------------------end---------------------"<<std::endl;
#else
        EndPoint* ep = new EndPoint(sock);
        ep->RecvHttpRequest();
        if(ep->IsStop())
        {
            LOG(WARNING, "Recv Error, Stop Build And Send");
        }
        else
        {
            LOG(INFO, "Recv No Error, Begin, Begin Build And Send");
            ep->BuildHttpResponse();
            ep->SendHttpResponse();
        }
        delete ep;
#endif
        LOG(INFO, "HandlerRequest ... end");
    }
};